從 ES5 的 Callback 以及它遺留下來的問題 Callback Hell ,到 ES6 的 Promise 以及 Generator,這之中都有所改進,但又往往留下了一點問題,終於到了 ES7 的 async/await,它更優雅的實現 Javascript 的異步操作,讓異步操作可以以同步化的方式進行。
一句話,它就是 Generator 的 語法糖
const fs = require('fs');
const readFile = function(filename) {
return new Promise(function (resolve, reject) {
fs.readFile(filename, (error, data) => {
return err ? reject(err) : resolve(data);
})
})
};
const gen = function* () {
const file1 = yield readFile('/file/a1');
const file2 = yield readFile('/file/b2');
console.log(file1.toString());
console.log(file2.toString());
}
// 上方這段程式碼寫成 async 函數,就是下面這樣
const AsyncMethodReadFile = async function() {
const file1 = await readFile('/file/a1');
const file2 = await readFile('/file/b2');
console.log(file1.toString());
console.log(file2.toString());
}
由上面例子就可以發現,其實 async = function*
,而 yield = await
。
async function f() {
return 'Hello Async';
}
f().then(a => console.log(a)); // f()返回值是 promise
//Hello Async
async function f() {
return await 123; // 因為不是接 promise 所以立即 resolve,回傳數值
}
f().then(a => console.log(a));
ES7 的 async 式表示函數都是異步的,返回值都是 Promise
,所以可以接續使用 then 添加 callback,
而 await 可以理解為 async wait,一定要寫在 async 內部,主要的作用就是用來等待 Promise 的狀態被 resolved,正常狀況下,await 後面接的是一個 Promise,如果不是,則會立即轉成一個 resolve 的 Promise。
在 Promise 的異常處理中,catch
並沒有解決 Callback 那個時代的問題(try-catch
無法捕捉異步的異常,需要寫很多個 debugger 去追蹤),Promise 的 catch
函數依舊不夠完整,難以獲得完整的異常訊息,以下面例子為例:
function getData() {
try {
requestData()
.then(result => {
const data = JSON.parse(result);
})
.catch((error) => {
console.log(error);
})
} catch (error) {
console.log(error);
}
}
在這段代碼中,try-catch可以捕捉到 Promise 內部的錯誤卻無法捕捉到 JSON.parse 拋出的錯誤,那如果要處理這項問題還需要添加 try-catch
去處理邏輯,如果今天是在更大型的項目,邏輯可能就會很混亂也會有不少冗余的程式。
以下方程式為例,如果是用 async/await 則可以使 try/catch 捕捉到同步和異步的錯誤;順帶一提,由於 await 後面接的 Promise 運行結果可能是 rejected
,所以最好把 await
放在 try/catch
中。
async function getData() {
try {
const data = JSON.parse(await requestData());
} catch (error) {
console.log(error);
}
}
下方程式(第一則)是通過 await 去獲得學生資料,其中 getName()
和 getLevel
是兩個獨立的異步操作,但是 getLevel
卻要等 getName resolved
後才能操作,這時候就會影響到效能,這裡要注意 async
說到底還是異步的,不要因為它有類似同步的操作而濫用它。
async function retriveUserDetail(url) {
let user = await getUser(url);
let name = await getName(user);
let level = await getLevel(user);
return [user,name,level]
}
// 以下兩種寫法 `getName` 跟 `getLevel`都是同時觸發
// 更正版寫法
async function retriveUserDetailCorrect(url) {
let user = await getUser(url);
let nameTemporarily = getName(user);
let levelTemporarily = getLevel(user);
let name = await nameTemporarily;
let level = await levelTemporarily;
return [user, roles, level];
}
// 更正版寫法
async function retriveUserDetailCorrect(url) {
let user = await getUser(url);
let [name, level] = await Promise.all([getName(user), getLevel(user)]);
return [user, roles, level];
}
可能會補上 generator+promise